/*===============================
 *Title      :  EncryptMachine.java
 *Created  :[ 2012-3-22 上午11:22:08 ] by fanlt
 * ==============================
 * Description:   
 * Copyright  :  Copyright (c) 2012
 * Company    :  Flyfot
 * @author  <a href="fanlt@flyfot.cn">fanlt</a>
 *
 */
package org.zxstudio.encrypt.task;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;

import org.zxstudio.encrypt.MediaBean;
import org.zxstudio.encrypt.provider.EncryptProvider;
import org.zxstudio.encrypt.tree.FileBean;
import org.zxstudio.encrypt.utils.Constant;
import org.zxstudio.encrypt.utils.RLog;
import org.zxstudio.encrypt.utils.SDUtils;

import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.util.Log;

/**
 * @author fan
 * 
 */
public class FileEncryptMachine extends EncryptMachine {

	private static final String TAG = "FileEncryptMachine";
	private static final boolean DEBUG = true;
	private static EncryptMachine sInstance = null;
	private EncryptListener mCallback = null;
	private Context mContext;

	public static final int _ID = 0;
	public static final int NAME = 1;
	public static final int STUB_NAME = 2;
	public static final int PATH = 3;
	public static final int STUB_PATH = 4;
	public static final int THUMBNAILS = 5;
	public static final int FILE_TYPE = 6;

	static Stack<File> mStack = new Stack<File>();

	protected static native boolean encrypt(String sourceName, String stubName);

	protected static native boolean decrypt(String sourceName, String stubName);

	static {
		Log.v("NativeEncryptMachine", "load libDoctorAn.so ");
		System.loadLibrary("DoctorAn");
	}

	private FileEncryptMachine(Context context) {
		mContext = context;
		createEncryptFolder();
	}

	public static EncryptMachine getInstance(Context context) {
		if (sInstance == null) {
			sInstance = new FileEncryptMachine(context);
		}
		mStack.clear();
		return sInstance;
	}

	/**
	 * 创建加密文件存放目录
	 */
	private void createEncryptFolder() {
		File target = new File(Constant.ENCRYPT_ROOT_PATH);
		if (!target.exists()) {
			target.mkdir();
		}
	}

	/**
	 * 对文件加密
	 * 
	 * @return
	 */
	private boolean encryptFile(File file) {

		if (DEBUG) {
			RLog.v(TAG, "开始加密文件");
		}
		FileBean bean = prepareBean(file);
		RLog.v(TAG,"encrypt file :");
		saveBean(bean);
		String sourcePath = bean.getPath();
		String encryptPath = bean.getStubPath();
		if (DEBUG) {
			RLog.v(TAG, " bean real path :" + sourcePath);
			RLog.v(TAG, "bean encrypt path :" + encryptPath);
		}

		boolean result = encrypt(sourcePath, encryptPath);
		if (result) {
			mCallback.addSucess(sourcePath);
		} else {
			mContext.getContentResolver().delete(EncryptProvider.ENCRYPT_URI,
					EncryptProvider.PATH + " = " + sourcePath, null);
			mCallback.addFailed(sourcePath);
		}
		return result;
	}

	/**
	 * 独立文件加密，根节点都为root_path
	 * 
	 * @param file
	 * @return
	 */
	private FileBean prepareBean(File file) {
		FileBean bean = new FileBean(file, Constant.ENCRYPT_ROOT_PATH);
		return bean;
	}

	/**
	 * 文件夹加密中对文件的加密
	 * 
	 * @param bean
	 * @return
	 */
	private boolean encryptFile(FileBean bean) {
		String sourceName = bean.getPath();
		String destName = bean.getStubPath();
		RLog.v(TAG, " bean real path :" + sourceName);
		RLog.v(TAG, "bean encrypt path :" + destName);
		boolean result = encrypt(sourceName, destName);
		if (result) {
			saveChildBean(bean);
			mCallback.addSucess(sourceName);
		} else {
			mCallback.addFailed(sourceName);
		}
		return result;
	}

	private void storeEncryptFile(String name, String stubName) {
		File sourceFile = new File(name);
	}

	/**
	 * 加密文件中有文件夹和文件
	 * 
	 * @return
	 */
	private boolean encryptFolder(File file) {
		// 指定加密文件的根目录的父节点为加密目录
		FileBean bean = new FileBean(file, Constant.ENCRYPT_ROOT_PATH);
		saveBean(bean);
		boolean result = mvFolderTree(bean);
		RLog.v(TAG,"encrypt folder : " + file.getAbsolutePath());
		RLog.v(TAG,"encrypt folder : " + result);
		return result;
	}

	/**
	 * 文件加密
	 * @param domain
	 * @return
	 */
	private boolean decryptFile(FileDomain domain) {
		RLog.v(TAG,"decryptFile : " + domain.path);
		if (domain != null) {
			File decryptFile = new File(domain.path);
			createParentDirectory(decryptFile);
			decrypt(domain.stub_path, domain.path);
			return true;
		}
		return false;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.zxstudio.encrypt.task.EncryptMachine#decryptFolder(java.io.File)
	 */
	private boolean decryptFolder(FileDomain domain) {
		File file = new File(domain.stub_path);
		String rootPath = Constant.getParentPath(domain.path);
		FileBean bean = new FileBean(file,rootPath);
		boolean result = mvFolderTree(bean);
		if (result) {
			saveBean(bean);
		}
		return result;
	}

	/**
	 * 
	 * 移动树形菜单到加密目录
	 * 
	 * @param node
	 * @return
	 */
	private boolean mvFolderTree(FileBean node) {
		if (node != null) {
			File file = new File(node.getStubPath());
			if (!file.exists()) {
				if (file.mkdir()) {
					Log.v(TAG, file.getPath() + " create sucess !");
				}
			}
		}
		for (FileBean bean : node.getChildList()) {
			if (bean.isFile()) {
				Log.v(TAG, bean.getPath() + "已经被删除");
				if (encryptFile(bean)) {
					bean.getFile().delete();
				}
			} else {
				if (!mvFolderTree(bean)) {
					return false;
				}
			}
		}

		if (!node.getFile().delete()) {
			return false;
		}

		return true;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * org.zxstudio.encrypt.task.EncryptMachine#registerEncryptListener(org.
	 * zxstudio.encrypt.task.EncryptListener)
	 */
	@Override
	public void registerEncryptListener(EncryptListener callback) {
		mCallback = callback;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.zxstudio.encrypt.task.EncryptMachine#encryptImage(java.io.File)
	 */
	private boolean encryptImage(File file) {
		if (DEBUG) {
			RLog.v(TAG, "开始加密图片文件");
		}
		FileBean bean = prepareBean(file);
		saveBean(bean);
		String sourcePath = bean.getPath();
		String encryptPath = bean.getStubPath();
		if (DEBUG) {
			RLog.v(TAG, " bean real path :" + sourcePath);
			RLog.v(TAG, "bean encrypt path :" + encryptPath);
		}

		boolean result = encrypt(sourcePath, encryptPath);
		if (result) {
			mCallback.addSucess(sourcePath);
		} else {
			mContext.getContentResolver().delete(EncryptProvider.ENCRYPT_URI,
					EncryptProvider.PATH + " = " + sourcePath, null);
			mCallback.addFailed(sourcePath);
		}
		return result;
	}

	/**
	 * 将加密文件信息保存到数据库
	 * 文件加密和文件夹加密中的首文件夹保存
	 * @param bean
	 */
	private void saveBean(FileBean bean) {
		RLog.v(TAG, "save image : " + bean.getName());
		ContentResolver cr = mContext.getContentResolver();
		ContentValues values = new ContentValues();
		values.put(EncryptProvider.NAME, bean.getName());
		values.put(EncryptProvider.STUB_NAME, bean.getStubName());
		values.put(EncryptProvider.PATH, bean.getPath());
		values.put(EncryptProvider.STUB_PATH, bean.getStubPath());
		byte[] thumbnails = null;
		int type = this.getDomainType(bean.getFile());
		if (type == Constant.IMAGE) {
			thumbnails = SDUtils.createImageThumbnail(bean.getPath());
		} else if (type == Constant.VIDEO) {
			thumbnails = SDUtils.getVideoThumbnail(bean.getPath());
			RLog.v(TAG,"encrypt file video thumbnails :" + (thumbnails == null));
		}

		if (thumbnails != null)
			values.put(EncryptProvider.THUMBNAILS, thumbnails);
		if (bean.isDirectory()) {
			values.put(EncryptProvider.FILE_TYPE, Constant.TYPE_FOLDER);
		} else {
			values.put(EncryptProvider.FILE_TYPE, Constant.TYPE_FILE);
		}
		
		cr.insert(EncryptProvider.ENCRYPT_URI, values);
	}
	
	/**
	 * 将加密文件信息保存到数据库
	 * 文件加密和文件夹加密中的首文件夹保存
	 * @param bean
	 */
	private void saveChildBean(FileBean bean) {
		RLog.v(TAG, " saveChildBean : " + bean.getName());
		ContentResolver cr = mContext.getContentResolver();
		ContentValues values = new ContentValues();
		values.put(EncryptProvider.NAME, bean.getName());
		values.put(EncryptProvider.STUB_NAME, bean.getStubName());
		values.put(EncryptProvider.PATH, bean.getPath());
		values.put(EncryptProvider.STUB_PATH, bean.getStubPath());
		values.put(EncryptProvider.FILE_TYPE, Constant.TYPE_CHILD);
		cr.insert(EncryptProvider.ENCRYPT_URI, values);
	}
	/*
	 * (non-Javadoc)
	 * 
	 * @see org.zxstudio.encrypt.task.EncryptMachine#encryptVideo(java.io.File)
	 */
	private boolean encryptVideo(File file) {
		if (DEBUG) {
			RLog.v(TAG, "开始加密图片文件");
		}
		FileBean bean = prepareBean(file);
		saveBean(bean);
		String sourcePath = bean.getPath();
		String encryptPath = bean.getStubPath();
		if (DEBUG) {
			RLog.v(TAG, " bean real path :" + sourcePath);
			RLog.v(TAG, "bean encrypt path :" + encryptPath);
		}

		boolean result = encrypt(sourcePath, encryptPath);
		if (result) {
			mCallback.addSucess(sourcePath);
		} else {
			mContext.getContentResolver().delete(EncryptProvider.ENCRYPT_URI,
					EncryptProvider.PATH + " = " + sourcePath, null);
			mCallback.addFailed(sourcePath);
		}
		return result;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.zxstudio.encrypt.task.EncryptMachine#encryptAudio(java.io.File)
	 */
	public boolean encryptAudio(File file) {
		// TODO Auto-generated method stub
		return false;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.zxstudio.encrypt.task.EncryptMachine#decryptImage(long)
	 */
	public boolean decryptImage(FileDomain domain) {
		RLog.v(TAG, "解密 decryptImage id :" + domain.path);
		if (domain != null) {
			File decryptFile = new File(domain.path);
			createParentDirectory(decryptFile);
			decrypt(domain.stub_path, domain.path);
			return true;
		}
		return false;
	}

	public void createParentDirectory(File file) {
		String parent = file.getParent();
		File pFile = new File(parent);
		if (!pFile.exists()) {
			mStack.push(pFile);
			createParentDirectory(pFile);
		}
		while (mStack.size() > 0) {
			File temp = mStack.pop();
			if (!temp.exists()) {
				temp.mkdir();
			}
		}
		return;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.zxstudio.encrypt.task.EncryptMachine#decryptVideo(long)
	 */
	public boolean decryptVideo(FileDomain domain) {
		// TODO Auto-generated method stub
		return false;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.zxstudio.encrypt.task.EncryptMachine#decryptAudio(long)
	 */
	public boolean decryptAudio(long id) {
		// TODO Auto-generated method stub
		return false;
	}

	private boolean isImage(File file) {
		return SDUtils.isImageFile(file);
	}

	private boolean isVideo(File file) {
		//return SDUtils.isVideoFile(mContext, file.getAbsolutePath());
		return SDUtils.isVideoFile(file);
	}

	@Override
	public boolean ecrypt(File file) {
		if (file.exists()) {
			if (file.isDirectory()) {
				RLog.v(TAG,"ecrypt : file " + file.getAbsolutePath());
				return encryptFolder(file);
			} else {
				return encryptFile(file);
			}
		}
		return false;
	}

	@Override
	public boolean decrypt(long id) {
		boolean result = false;
		FileDomain domain = this.prepareDomain(id);
		if (domain != null) {
			if (domain.file_type == Constant.TYPE_FOLDER) {
				result = this.decryptFolder(domain);
			} else {
				result = this.decryptFile(domain);
			}
		}
		if (result) {
			deleteDomain(id);
		}
		return result;
	}

	private int getDomainType(File file) {
		if (file.exists()) {
			if (this.isImage(file)) {
				return Constant.IMAGE;
			} else if (this.isVideo(file)) {
				return Constant.VIDEO;
			}
		}
		return -1;
	}

	private void deleteDomain(long id) {
		Uri url = ContentUris.withAppendedId(EncryptProvider.ENCRYPT_URI, id);
		mContext.getContentResolver().delete(url, null, null);
	}
	
	private FileDomain prepareDomain(long id) {
		Uri uri = ContentUris.withAppendedId(EncryptProvider.ENCRYPT_URI, id);
		Cursor cursor = mContext.getContentResolver().query(uri,
				EncryptProvider.sPROJECTIONS, null, null, null);
		if (cursor != null && cursor.getCount() > 0) {
			if (cursor.moveToFirst()) {
				FileDomain domain = new FileDomain();
				domain._id = id;
				domain.name = cursor.getString(NAME);
				domain.stub_name = cursor.getString(STUB_NAME);
				domain.path = cursor.getString(PATH);
				domain.stub_path = cursor.getString(STUB_PATH);
				domain.thumbnails = cursor.getBlob(THUMBNAILS);
				domain.file_type = cursor.getInt(FILE_TYPE);
				domain.uri = uri;
				return domain;

			}
			return null;
		}
		return null;
	}

	private class FileDomain {
		public long _id;
		public String name;
		public String stub_name;
		public String path;
		public String stub_path;
		public byte[] thumbnails;
		public int file_type;
		public Uri uri;
	}
}
